using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Permissions;
using System.Threading;
using OracleInternal.Common;
using OracleInternal.I18N;

namespace OracleInternal.Network
{
	internal class TcpTransportAdapter : ITransportAdapter
	{
		protected TcpClient m_client;

		protected TcpListener m_listener;

		protected string m_host;

		protected int m_portNo = -1;

		protected bool m_Connected;

		protected ConnectionOption m_conOption;

		protected OraBuf.AsyncReceiveCallback m_AsyncRecvCB;

		protected ConOraBufPool m_OraBufPool;

		protected int m_OraBufSize;

		protected object m_OraBufInitArg;

		protected IAsyncResult m_ar;

		private IPAddress[] addresses;

		private bool https_proxy;

		private int iterator;

		protected Socket[] m_listsocks = new Socket[2];

		protected bool m_v6OnlyOff;

		protected bool m_DblListen;

		private bool m_EOF;

		private static int m_DefListenBacklog = 50;

		private IAsyncResult[] m_AcceptARs = new IAsyncResult[2];

		protected byte[] m_AsyncBuffer;

		protected OraBuf m_OraBuf;

		protected object m_discLock = new object();

		private const int BytesPerLong = 4;

		private const int BitsPerByte = 8;

		public ConOraBufPool OraBufPool
		{
			set
			{
				m_OraBufPool = value;
			}
		}

		public bool Connected
		{
			get
			{
				if (!m_client.Connected)
				{
					return false;
				}
				if (m_client.Client.Poll(0, SelectMode.SelectError))
				{
					return false;
				}
				if (m_client.Client.Poll(0, SelectMode.SelectRead) && m_client.Client.Available == 0)
				{
					return false;
				}
				return true;
			}
		}

		public virtual bool NeedReneg => false;

		internal TcpTransportAdapter(NameValueCollection socketOptions)
		{
		}

		internal TcpTransportAdapter(ConnectionOption conOption, TcpClient tcpClient)
		{
			m_client = tcpClient;
			m_Connected = true;
			Initialize(conOption);
		}

		public virtual void Connect(ConnectionOption conOption)
		{
			if (ProviderConfig.m_bTraceLevelNetwork)
			{
				Trace.Write(OracleTraceLevel.Network, OracleTraceTag.Entry, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.Connect);
			}
			Initialize(conOption);
			if (!string.IsNullOrEmpty(conOption.HTTPS_PROXY))
			{
				m_host = conOption.HTTPS_PROXY;
				m_portNo = conOption.HTTPS_PROXY_PORT;
				https_proxy = true;
			}
			else
			{
				m_portNo = conOption.Port;
				if (conOption.Host == null)
				{
					m_host = "";
				}
				else
				{
					m_host = conOption.Host;
				}
			}
			if (conOption.IP != null && string.Equals(conOption.IP, "loopback", StringComparison.InvariantCultureIgnoreCase))
			{
				addresses = new IPAddress[1];
				addresses[0] = IPAddress.Loopback;
			}
			else if (m_host.Contains(":"))
			{
				addresses = new IPAddress[1];
				addresses[0] = IPAddress.Parse(m_host);
			}
			else
			{
				try
				{
					if ((addresses = Dns.GetHostAddresses(m_host)) == null)
					{
						throw new NetworkException(12545);
					}
				}
				catch (Exception inner)
				{
					throw new NetworkException(12545, inner);
				}
			}
			if (ProviderConfig.m_bTraceLevelNetwork)
			{
				Trace.Write(OracleTraceLevel.Network, OracleTraceTag.None, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.Connect, "Trying (host=" + m_host + ") (port=" + m_portNo + ")");
			}
			if (ProviderConfig.m_bTraceLevelNetwork)
			{
				Trace.Write(OracleTraceLevel.Network, OracleTraceTag.Exit, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.Connect);
			}
		}

		public virtual Stream ConnectIterate()
		{
			SqlNetOraConfig sNOConfig = m_conOption.SNOConfig;
			IPAddress iPAddress = null;
			if (ProviderConfig.m_bTraceLevelNetwork)
			{
				Trace.Write(OracleTraceLevel.Network, OracleTraceTag.Entry, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.ConnectIterate);
			}
			if (addresses == null || addresses.Length == 0 || iterator >= addresses.Length)
			{
				return null;
			}
			try
			{
				iPAddress = addresses[iterator++];
				m_client = new TcpClient(iPAddress.AddressFamily);
				if (ProviderConfig.m_bTraceLevelNetwork)
				{
					Trace.Write(OracleTraceLevel.Network, OracleTraceTag.None, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.ConnectIterate, "Trying (address=" + iPAddress?.ToString() + ") (port=" + m_portNo + ")");
				}
				if (!string.IsNullOrEmpty(sNOConfig["tcp.nodelay"]))
				{
					m_client.Client.NoDelay = sNOConfig.TCPNoDelay;
				}
				else
				{
					m_client.Client.NoDelay = sNOConfig.NoDelay;
				}
				if (m_conOption.KeepAlive)
				{
					string strA = Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
					bool flag;
					if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
					{
						if (ProviderConfig.m_bTraceLevelNetwork)
						{
							Trace.Write(OracleTraceLevel.Network, OracleTraceTag.None, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.ConnectIterate, "KeepAlive on Linux/MacOS");
						}
						if (string.Compare(strA, 0, ".NETCoreApp,Version=v2", 0, 22, StringComparison.InvariantCultureIgnoreCase) == 0)
						{
							flag = false;
							if (ProviderConfig.m_bTraceLevelNetwork)
							{
								Trace.Write(OracleTraceLevel.Network, OracleTraceTag.None, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.ConnectIterate, "Linux/MacOS have no Keepalive on Core2");
							}
						}
						else
						{
							flag = SetKeepAlive_Core3(m_client.Client, m_conOption.m_KeepAliveTime, m_conOption.m_KeepAliveInterval);
						}
					}
					else
					{
						flag = SetKeepAlive(m_client.Client, m_conOption.KeepAliveTime, m_conOption.KeepAliveInterval);
					}
					if (ProviderConfig.m_bTraceLevelNetwork)
					{
						Trace.Write(OracleTraceLevel.Network, OracleTraceTag.None, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.ConnectIterate, "Turning on Keepalive " + (flag ? "succeeded: " : "failed: ") + "Time = " + m_conOption.KeepAliveTime + ", Interval = " + m_conOption.KeepAliveInterval);
					}
				}
				if (m_conOption.SBS != 0)
				{
					m_client.Client.SendBufferSize = m_conOption.SBS;
				}
				if (m_conOption.RBS != 0)
				{
					m_client.Client.ReceiveBufferSize = m_conOption.RBS;
				}
				IAsyncResult asyncResult = m_client.BeginConnect(iPAddress, m_portNo, null, null);
				bool flag2 = asyncResult.AsyncWaitHandle.WaitOne(m_conOption.TransportConnectTO, exitContext: false);
				m_Connected = m_client.Client.Connected;
				if (!(m_Connected || flag2))
				{
					BadIPCache.Insert(iPAddress.ToString());
					if (m_client.Client != null)
					{
						m_client.Client.Close();
					}
					throw new NetworkException(12607);
				}
				m_client.Client.EndConnect(asyncResult);
				if (m_Connected)
				{
					if (https_proxy)
					{
						Handle_HTTPS_PROXY(m_conOption);
					}
					return m_client.GetStream();
				}
			}
			catch (SocketException ex)
			{
				BadIPCache.Insert(iPAddress.ToString());
				if (m_client.Client != null)
				{
					m_client.Client.Close();
				}
				if (ex.ErrorCode == 10061)
				{
					throw new NetworkException(12541, ex);
				}
				throw new NetworkException(-6403, ex);
			}
			catch (NetworkException ex2)
			{
				BadIPCache.Insert(iPAddress.ToString());
				throw ex2;
			}
			catch (Exception inner)
			{
				BadIPCache.Insert(iPAddress.ToString());
				if (m_client.Client != null)
				{
					m_client.Client.Close();
				}
				throw new NetworkException(-6403, inner);
			}
			if (ProviderConfig.m_bTraceLevelNetwork)
			{
				Trace.Write(OracleTraceLevel.Network, OracleTraceTag.Exit, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.ConnectIterate);
			}
			if (!m_Connected)
			{
				throw new NetworkException(-6403);
			}
			return m_client.GetStream();
		}

		private void Initialize(ConnectionOption conOption)
		{
			m_conOption = conOption;
			m_OraBufPool = conOption.AsyncBufferPool;
			m_OraBufInitArg = conOption.AsyncBufferInitArg;
			if (m_OraBufPool == null)
			{
				throw new NetworkException(12532);
			}
		}

		[SecurityPermission(SecurityAction.Assert, Unrestricted = true)]
		private void StartListening(IPAddress addr, int i)
		{
			m_listsocks[i] = new Socket(addr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
			if (m_v6OnlyOff)
			{
				m_listsocks[i].SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, optionValue: false);
			}
			m_listsocks[i].Bind(new IPEndPoint(addr, m_portNo));
			m_listsocks[i].Listen(m_DefListenBacklog);
		}

		public void Listen(ConnectionOption conOption)
		{
			Initialize(conOption);
			try
			{
				m_portNo = conOption.Port;
				IPAddress iPAddress;
				if (conOption.Host == null || conOption.Host == "" || string.Compare(conOption.Host, Dns.GetHostName(), ignoreCase: true) == 0 || conOption.inAddr_Any)
				{
					m_host = "";
					if (Environment.OSVersion.Version.Major >= 6)
					{
						iPAddress = IPAddress.IPv6Any;
						m_v6OnlyOff = true;
					}
					else if (NetEnvironment.gotIPv4)
					{
						iPAddress = IPAddress.Any;
						if (NetEnvironment.gotIPv6)
						{
							m_DblListen = true;
						}
					}
					else
					{
						iPAddress = IPAddress.IPv6Any;
					}
				}
				else
				{
					m_host = conOption.Host;
					iPAddress = ((!m_host.Contains(":")) ? Dns.GetHostAddresses(m_host)[0] : IPAddress.Parse(m_host));
				}
				StartListening(iPAddress, 0);
				if (ProviderConfig.m_bTraceLevelNetwork)
				{
					Trace.Write(OracleTraceLevel.Network, OracleTraceTag.None, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.Listen, "Notification Listener started on (address=" + iPAddress?.ToString() + ") (port=" + m_portNo + ")");
				}
				if (m_DblListen)
				{
					StartListening(IPAddress.IPv6Any, 1);
					m_AcceptARs[0] = m_listsocks[0].BeginAccept(null, null);
					m_AcceptARs[1] = m_listsocks[1].BeginAccept(null, null);
				}
			}
			catch (SocketException ex)
			{
				throw ex;
			}
		}

		private Socket DblListenAccept()
		{
			int num = WaitHandle.WaitAny(new WaitHandle[2]
			{
				m_AcceptARs[0].AsyncWaitHandle,
				m_AcceptARs[1].AsyncWaitHandle
			});
			Socket result = m_listsocks[num].EndAccept(m_AcceptARs[num]);
			m_AcceptARs[num] = m_listsocks[num].BeginAccept(null, null);
			return result;
		}

		public virtual ITransportAdapter Answer(ConnectionOption conOption)
		{
			Initialize(conOption);
			try
			{
				if (m_listsocks[0] == null || (m_DblListen && m_listsocks[1] == null))
				{
					throw new NetworkException(-6002);
				}
				Socket client = (m_DblListen ? DblListenAccept() : m_listsocks[0].Accept());
				TcpClient tcpClient = new TcpClient();
				tcpClient.Client = client;
				return new TcpTransportAdapter(conOption, tcpClient);
			}
			catch (SocketException)
			{
				throw;
			}
		}

		public virtual void Disconnect()
		{
			lock (m_discLock)
			{
				try
				{
					if (m_client != null)
					{
						m_client.Client.Close();
						m_client.Close();
						m_client = null;
					}
					if (m_listener != null)
					{
						m_listener.Stop();
						m_listener = null;
					}
				}
				catch (SocketException)
				{
				}
			}
		}

		public virtual Stream GetStream()
		{
			if (m_client != null)
			{
				return m_client.GetStream();
			}
			return null;
		}

		public virtual Socket GetSocket()
		{
			if (m_client != null)
			{
				return m_client.Client;
			}
			return null;
		}

		public virtual Socket GetTestSocket()
		{
			return GetSocket();
		}

		public virtual bool UrgentDataSupported()
		{
			return true;
		}

		private void Send(OraArraySegment[] OAS, int OASLength)
		{
			for (int i = 0; i < OASLength; i++)
			{
				m_client.Client.Send(OAS[i].Array, OAS[i].Offset, OAS[i].Count, SocketFlags.None);
			}
		}

		public virtual void Send(OraBuf OB)
		{
			try
			{
				if (OB.the_ByteSegments_Count == 2 && OB.the_ByteSegments[0].Array == OB.the_ByteSegments[1].Array)
				{
					m_client.Client.Send(OB.m_buf, 0, OB.m_curlen, SocketFlags.None);
				}
				else
				{
					Send(OB.the_ByteSegments, OB.the_ByteSegments_Count);
				}
			}
			catch (Exception inner)
			{
				throw new NetworkException(12571, inner);
			}
		}

		public virtual void BeginAsyncReceives(OraBuf.AsyncReceiveCallback myCallback, int AsyncBufferSize)
		{
			m_OraBufSize = AsyncBufferSize;
			m_AsyncRecvCB = myCallback;
		}

		public void SendUrgent(byte[] data, int offset, int length)
		{
			m_client.Client.Send(data, offset, length, SocketFlags.OutOfBand);
		}

		public virtual void Renegotiate(ConnectionOption conOption)
		{
		}

		internal void Handle_HTTPS_PROXY(ConnectionOption co)
		{
			byte[] array = new byte[256];
			Conv instance = Conv.GetInstance(871);
			Stream stream = m_client.GetStream();
			if (stream == null)
			{
				throw new NetworkException(12533);
			}
			string text = NetworkException.sprintf("CONNECT %s:%d HTTP/1.1\r\nHOST: %s:%d\r\n\r\n", co.Host, co.Port, co.Host, co.Port);
			if (ProviderConfig.m_bTraceLevelNetwork)
			{
				Trace.Write(OracleTraceLevel.Network, OracleTraceTag.None, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.Handle_HTTPS_PROXY, text);
			}
			stream.Write(instance.ConvertStringToBytes(text, 0, text.Length), 0, text.Length);
			int byteCount = stream.Read(array, 0, 256);
			string text2 = instance.ConvertBytesToString(array, 0, byteCount);
			if (ProviderConfig.m_bTraceLevelNetwork)
			{
				Trace.Write(OracleTraceLevel.Network, OracleTraceTag.None, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.Handle_HTTPS_PROXY, text2);
			}
		}

		private static bool SetKeepAlive_Core3(Socket sock, int time, int interval)
		{
			try
			{
				Assembly assembly = Assembly.Load("System.Net.Sockets");
				Type type = assembly.GetType("System.Net.Sockets.socket", throwOnError: true, ignoreCase: true);
				Type type2 = assembly.GetType("System.Net.Sockets.SocketOptionLevel");
				Type type3 = assembly.GetType("System.Net.Sockets.SocketOptionName");
				MethodInfo method = type.GetMethod("SetSocketOption", new Type[3]
				{
					type2,
					type3,
					typeof(bool)
				});
				MethodInfo method2 = type.GetMethod("SetSocketOption", new Type[3]
				{
					type2,
					type3,
					typeof(int)
				});
				method.Invoke(sock, new object[3]
				{
					65535,
					8,
					true
				});
				method2.Invoke(sock, new object[3]
				{
					6,
					16,
					10
				});
				method2.Invoke(sock, new object[3]
				{
					6,
					3,
					time / 1000
				});
				method2.Invoke(sock, new object[3]
				{
					6,
					17,
					interval / 1000
				});
			}
			catch (Exception ex)
			{
				if (ProviderConfig.m_bTraceLevelNetwork)
				{
					Trace.Write(OracleTraceLevel.Network, OracleTraceTag.None, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.SetKeepAlive, "SetKeepAlive_Core3 failed: " + ex.ToString());
				}
				return false;
			}
			return true;
		}

		private bool SetKeepAlive(Socket sock, int time, int interval)
		{
			try
			{
				byte[] array = new byte[12];
				int[] array2 = new int[3];
				if (time == 0 || interval == 0)
				{
					array2[0] = 0;
				}
				else
				{
					array2[0] = 1;
				}
				array2[1] = time;
				array2[2] = interval;
				for (int i = 0; i < array2.Length; i++)
				{
					array[i * 4 + 3] = (byte)((uint)(array2[i] >> 24) & 0xFFu);
					array[i * 4 + 2] = (byte)((uint)(array2[i] >> 16) & 0xFFu);
					array[i * 4 + 1] = (byte)((uint)(array2[i] >> 8) & 0xFFu);
					array[i * 4] = (byte)((uint)array2[i] & 0xFFu);
				}
				byte[] bytes = BitConverter.GetBytes(0);
				sock.IOControl(IOControlCode.KeepAliveValues, array, bytes);
				return BitConverter.ToInt32(bytes, 0) == 0;
			}
			catch (Exception ex)
			{
				if (ProviderConfig.m_bTraceLevelNetwork)
				{
					Trace.Write(OracleTraceLevel.Network, OracleTraceTag.None, OracleTraceClassName.TcpTransportAdapter, OracleTraceFuncName.SetKeepAlive, "SetKeepAlive failed: " + ex.ToString());
				}
				return false;
			}
		}
	}
}
